[Amazon SageMaker] Amazon SageMaker Ground Truth で作成したデータをOpenCVで増幅してみました
1 はじめに
CX事業本部の平内(SIN)です。
オブジェクト検出では、大量のデータセットが必要ですが、これを準備する作業量は、結構大変です。そのため、データ(画像)を増幅してデータ数を増加させる手法があります。
今回は、OpenCVによる画像処理で、Amazon SageMaker Ground Truth(以下、Ground Truth)で作成したデータを増幅してみました。
なお、画像の回転や、反転などを行うと、Ground Truthで設定したアノテーションを、やり直す必要が生じるので、ここでは、アノテーションが変化しない変換だけを対象にしました。
2 変換ファンクション
試した変換の種類について列挙します。下記が、サンプルのために使用した、800 × 600の元画像です。
(1) 彩度
彩度は、BGR形式を、一旦、HSV形式に変換して変換しています。パラメータに1.0を与えると無変換となります。
import cv2 import numpy as np from matplotlib import pyplot as plt img = cv2.imread('./test.jpg') # 彩度 def saturation(src, saturation): # 一旦、BGRをHSVに変換して彩度を変換する img = cv2.cvtColor(src,cv2.COLOR_BGR2HSV) img[:,:,(1)] = img[:,:,(1)] * saturation img = cv2.cvtColor(img,cv2.COLOR_HSV2BGR) return img # 表示 def view(img, func, params): w = 5 # 横に5つ並べる h = int(len(params)/w)+1 fig = plt.figure(figsize=(10, 7)) for i,p in enumerate(params): dst = func(img, p) ax = fig.add_subplot(h, w, i + 1) ax.set_axis_off() ax.imshow(cv2.cvtColor(dst, cv2.COLOR_BGR2RGB)) # BGR => RGB ax.set_title(f"param={p:.1f}") plt.show() view(img, saturation, [0, 0.2, 0.4, 0.6, 0.8, 1.0, 1.2, 1.4, 1.6,1.8,2.0,3.0])
パラメータは、0.4〜3.0ぐらいが変換として使用できそうです。
下記は、0.2での例です。
3.0での例です。
(2) 明度
明度についても、HSV形式に変換して変換しています。パラメータに1.0を与えると無変換となります。
# 明度 def brightness(src, brightness): # 一旦、BGRをHSVに変換して明度を変換する img = cv2.cvtColor(src,cv2.COLOR_BGR2HSV) img[:,:,(2)] = img[:,:,(2)] * brightness img = cv2.cvtColor(img,cv2.COLOR_HSV2BGR) return img
パラメータは、0.4〜0.8ぐらいが変換として使用できそうです。
0.6での変換例です。
(3) コントラスト
各色相をalphaで演算しています。パラメータに1.0を与えると無変換となります。
# コントラスト def contrast(src, alpha): # 各色相をalphaで演算する img = alpha * src return np.clip(img, 0, 255).astype(np.uint8)
パラメータは、0.6〜2.0ぐらいが変換として使用できそうです。
0.8で変換
2.0で変換
(4) モザイク
全体をリサイズして戻すことでモザイクにしています。パラメータに1.0を与えると無変換となります。 実質的に、ピクセル数の小さなデータセットとなるため、小さく映り込む物体の検出用に有効でかも知れません。なお、パラメータは、想定する検出状況と元データの解像度を考慮して決定する必要があります。
# モザイク def mosaic(src, ratio): # ratio倍でリサイズして戻す img = cv2.resize(src, None, fx=ratio, fy=ratio, interpolation=cv2.INTER_NEAREST) return cv2.resize(img, src.shape[:2][::-1], interpolation=cv2.INTER_NEAREST)
今回の例となっている800 × 600 では、パラメータ0.05ぐらいまでが有効になりそうです。
0.1で変換したものです。
(5) ガウスノイズ
ガウスノイズです。パラメータに1.0を与えると無変換となります。
# ガウスノイズ def gaussian(src, sigma): # ランダム値で生成した画像と合成する row,col,ch = src.shape mean = 0 gauss = np.random.normal(mean, sigma, (row, col, ch)).astype('u1') gauss = gauss.reshape(row, col, ch) return src + gauss
有効なパラメータは、10〜100ぐらいでしょうか。
20での変換例です。
(6) ごま塩ノイズ
ランダム値で生成した画像を合成しています。
# ごま塩ノイズ def noise(src, amount): # ランダム値で生成したノイズと合成する img = src.copy() num_pepper = np.ceil(amount* src.size * (0.5)) coords = [np.random.randint(0, i-1 , int(num_pepper)) for i in src.shape] img[coords[:-1]] = (0,0,0) return img
パラメータ0.1で変換した例です。
3 増幅
ここまでの変換用ファンクションを使用して、Ground Truth のデータを増幅しているコードです。 変換リストに、ファンクション名とパラメータをセットすることで増幅の内容を指定できます。
amplify.py
def main(): # 変換リスト convertList = [ {"function": saturation, "param": 0.2}, # 彩度 {"function": saturation, "param": 3.0}, {"function": brightness, "param": 0.6}, # 明度 {"function": contrast, "param": 0.8}, # コントラスト {"function": mosaic, "param": 0.05},# モザイク {"function": mosaic, "param": 0.1}, {"function": mosaic, "param": 0.2}, {"function": gaussian, "param": 20.0},# ガウスノイズ {"function": noise, "param": 0.1}, # ごま塩ノイズ ] # 元データの情報を取得する dataList = getDataList(targetPath, manifest) print("全データ: {}件 ".format(len(dataList))) outputManifest = '' for data in dataList: # 元データのエクスポート outputManifest += data.dumps() + '\n' # 元データの画像 srcImage = "{}/{}.{}".format(targetPath, data.baseName, data.ext) orgBaseName = data.baseName # 変換リストに基づく増幅処理 for i,convert in enumerate(convertList): # 元データの名前取得 baseName = "{}-{}".format(orgBaseName, str(i+1).zfill(3)) # 出力データの画像ファイル名 dstImage = "{}/{}.{}".format(targetPath, baseName, data.ext) # 元データの名前を変更 data.baseName = baseName # 変換データのエクスポート outputManifest += data.dumps() + '\n' # 画像の変換処理 img = cv2.imread(srcImage) img = convert["function"](img, convert["param"]) # 変換後の画像のエクスポート cv2.imwrite(dstImage,img) # Manifestファイルのエクスポート with open("{}/{}".format(targetPath, manifest), mode='w') as f: f.write(outputManifest) print("増幅後データ: {}件 ".format(len(outputManifest.split('\n')))) main()
実行すると、下記のように4件のデータが約10倍になっていることを確認できます。
$ python3 amplify.py 全データ: 4件 増幅後データ: 41件
4 最後に
今回は、Ground Truthのデータを増幅してみました。この変換では、改めてアノテーションする作業は発生しないので、作業時間は、殆ど必要ないでしょう。 なお、データの増幅は、最終的なオブジェクト検出の条件を考慮して、注意深く行う必要があると思います。
すべてのコードは、下記に起きました。
https://gist.github.com/furuya02/6b10263c77d0b62b492f96f107d38dc7